﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Spacer {
	public enum RenderMode {
		None,
		Screen,
		World
	}

	/// <summary>
	/// Containers constants for preprogrammed layer values
	/// SUBLAYERS are a 100th the size, from 0.0010 to 0.0050, for tweaking by addition or subtraction
	/// </summary>
	public static class Layer {
		public const float LAYER_0  = 0.00f;
		public const float LAYER_1  = 0.05f;
		public const float LAYER_2  = 0.10f;
		public const float LAYER_3  = 0.15f;
		public const float LAYER_4  = 0.20f;
		public const float LAYER_5  = 0.25f;
		public const float LAYER_6  = 0.30f;
		public const float LAYER_7  = 0.35f;
		public const float LAYER_8  = 0.40f;
		public const float LAYER_9  = 0.45f;
		public const float LAYER_10 = 0.50f;
		public const float LAYER_11 = 0.55f;
		public const float LAYER_12 = 0.60f;
		public const float LAYER_13 = 0.65f;
		public const float LAYER_14 = 0.70f;
		public const float LAYER_15 = 0.75f;
		public const float LAYER_16 = 0.80f;
		public const float LAYER_17 = 0.85f;
		public const float LAYER_18 = 0.90f;
		public const float LAYER_19 = 0.95f;
		public const float LAYER_20 = 1.00f;
		
		// These layers are for easy tweaking when needed
		// Usage: Layer.LAYER_0 <+ OR -> Layer.SUBLAYER_1
		
		public const float SUBLAYER_1  =  0.0010f;
		public const float SUBLAYER_2  =  0.0020f;
		public const float SUBLAYER_3  =  0.0030f;
		public const float SUBLAYER_4  =  0.0040f;
		public const float SUBLAYER_5  =  0.0050f;

		public const float WORLD_BACKGROUND = 0f;
	}

	// ----------

	public static class Surface {
		public static void Initialize( GraphicsDevice device ) {
			s_GraphicsDevice = device;
			SpriteBatch = new SpriteBatch( device );

			s_SpriteEffect = new BasicEffect( device );
			s_SpriteEffect.TextureEnabled = true;
			s_SpriteEffect.VertexColorEnabled = true;

			s_TextureMissing = Main.Textures["Missing"];
			s_TextureWhite = Main.Textures["White"];

			RenderMode = RenderMode.None;
			DrawColor = Color.White;
			WorldScale = 32f;
			ZoomScale = 0.7f;
		}

		/// <summary>
		/// INTERNAL USE
		/// Sets Draw to be in a ready state to accept render calls.
		/// Screen coords
		/// </summary>
		public static void BeginOverlay() {
			if( SpriteBatch == null || RenderMode != RenderMode.None )
				return;
			RenderMode = RenderMode.Screen;

			#if XBOX360
			OverscanMatrix = Matrix.Identity;//Matrix.CreateTranslation(Surface.Width*-0.5f,Surface.Height*-0.5f,0f) * Matrix.CreateScale(0.95f) * Matrix.CreateTranslation(Surface.Width*0.5f,Surface.Height*0.5f,0f);
			#else
			OverscanMatrix = Matrix.Identity;
			#endif

			WorldMatrix = Matrix.Identity;
			ViewMatrix = OverscanMatrix;
			ProjectionMatrix = Matrix.CreateOrthographicOffCenter( 0f, Surface.Width, Surface.Height, 0f, -1f, 1f );
			
			s_SpriteEffect.World = WorldMatrix;
			s_SpriteEffect.View = ViewMatrix;
			s_SpriteEffect.Projection = ProjectionMatrix;
			SpriteBatch.Begin( SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.AnisotropicWrap, DepthStencilState.DepthRead, RasterizerState.CullNone, s_SpriteEffect );
		}

		/// <summary>
		/// INTERNAL USE
		/// Sets Draw to be in a ready state to accept render calls.
		/// World coords
		/// </summary>
		public static void BeginWorld() {
			if( SpriteBatch == null || RenderMode != RenderMode.None )
				return;
			RenderMode = RenderMode.World;

			float hw = (Surface.Width / (Surface.WorldScale*ZoomScale)) * 0.5f;
			float hh = (Surface.Height / (Surface.WorldScale*ZoomScale)) * 0.5f;

			if( Globals.LocalPlayer != null && Globals.LocalPlayer.CurrentShip != null )
				WorldMatrix = Matrix.CreateTranslation( new Vector3(Globals.LocalPlayer.CurrentShip.Position*-1,0f) );
			else
				WorldMatrix = Matrix.Identity;

			ViewMatrix = Matrix.Identity;
			ProjectionMatrix = Matrix.CreateOrthographicOffCenter( -hw, hw, -hh, hh, -1f, 1f );

			s_SpriteEffect.World = WorldMatrix;
			s_SpriteEffect.View = ViewMatrix;
			s_SpriteEffect.Projection = ProjectionMatrix;
			SpriteBatch.Begin( SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.AnisotropicWrap, DepthStencilState.DepthRead, RasterizerState.CullNone, s_SpriteEffect );
		}

		/// <summary>
		/// INTERNAL USE
		/// Finalizes render calls and renders to the screen
		/// </summary>
		public static void End() {
			if( SpriteBatch == null || RenderMode == RenderMode.None )
				return;
			RenderMode = RenderMode.None;

			SpriteBatch.End();
		}

		/// <summary>
		/// Draws a textured rectangle to the screen modulated by the set color.
		/// </summary>
		/// <param name="tex">Texture to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="size">Size to render texture as</param>
		/// <param name="origin">Offset from the texture origin in texture coords to draw and rotate around</param>
		/// <param name="rotation">Rotation</param>
		public static void DrawRect( Texture2D tex, Vector2 pos, Vector2 size, Vector2 origin, float rotation ) { DrawRect(tex,pos,size,origin,rotation,0f); }
		public static void DrawRect( Texture2D tex, Vector2 pos, Vector2 size, Vector2 origin, float rotation, float depth ) {
			if( SpriteBatch == null || RenderMode == RenderMode.None )
				return;

			if( tex == null )
				tex = s_TextureMissing;

			if( RenderMode == RenderMode.World ) {
				size.X = size.X / tex.Width / Surface.WorldScale;
				size.Y = size.Y / tex.Height / Surface.WorldScale;
			} else {
				size.X = size.X / tex.Width;
				size.Y = size.Y / tex.Height;
				rotation *= -1f;
			}

			SpriteBatch.Draw(
				tex,
				pos,
				null,
				DrawColor,
				rotation,
				origin,
				size,
				(RenderMode == RenderMode.World ? SpriteEffects.FlipVertically : SpriteEffects.None),
				depth
			);
		}

		/// <summary>
		/// Draws a rectangle to the screen modulated by the set color.
		/// Only a simplification for flat colors.
		/// </summary>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="size">Size to render texture as</param>
		/// <param name="origin">Offset from the texture origin in texture coords to draw and rotate around</param>
		/// <param name="rotation">Rotation</param>
		public static void DrawRect( Vector2 pos, Vector2 size, Vector2 origin, float rotation ) { DrawRect(pos,size,origin,rotation,0f); }
		public static void DrawRect( Vector2 pos, Vector2 size, Vector2 origin, float rotation, float depth ) {
			DrawRect( s_TextureWhite, pos, size, origin, rotation, depth );
		}

		/// <summary>
		/// Draws a line to the screen modulated by the set color.
		/// </summary>
		/// <param name="pos1">Offset from the screen origin, start</param>
		/// <param name="pos2">Offset from the screen origin, end</param>
		/// <param name="weight">Width of line</param>
		public static void DrawLine( Vector2 pos1, Vector2 pos2, float weight ) { DrawLine(pos1,pos2,weight,0f); }
		public static void DrawLine( Vector2 pos1, Vector2 pos2, float weight, float depth ) {
			weight = (weight > 0f ? weight : 1f);
			DrawRect( s_TextureWhite, pos1, new Vector2((pos2-pos1).Length(),weight), new Vector2(0f,0.5f), -(float)Math.Atan2( pos2.Y-pos1.Y, pos2.X-pos1.X ), depth );
		}

		/// <summary>
		/// Draws a textured rectangle to the screen modulated by the set color.
		/// </summary>
		/// <param name="tex">Texture to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="size">Size to render texture as</param>
		/// <param name="sub">Area within the texture provided to draw</param>
		/// <param name="origin">Offset from the texture origin in texture coords to draw and rotate around</param>
		/// <param name="rotation">Rotation</param>
		public static void DrawSubRect( Texture2D tex, Vector2 pos, Vector2 size, Rectangle sub, Vector2 origin, float rotation ) { DrawSubRect(tex,pos,size,sub,origin,rotation,0f); }
		public static void DrawSubRect( Texture2D tex, Vector2 pos, Vector2 size, Rectangle sub, Vector2 origin, float rotation, float depth ) {
			if( SpriteBatch == null || RenderMode == RenderMode.None )
				return;

			if( tex == null )
				tex = s_TextureMissing;

			if( RenderMode == RenderMode.World ) {
				size.X = size.X / sub.Width / Surface.WorldScale;
				size.Y = size.Y / sub.Height / Surface.WorldScale;
			} else {
				size.X = size.X / sub.Width;
				size.Y = size.Y / sub.Height;
				rotation *= -1f;
			}

			SpriteBatch.Draw(
				tex,
				pos,
				sub,
				DrawColor,
				rotation,
				origin,
				size,
				(RenderMode == RenderMode.World ? SpriteEffects.FlipVertically : SpriteEffects.None),
				depth
			);
		}

		/// <summary>
		/// Draws a textured rectangle to the screen modulated by the set color and it is stretched along the verticle and horizontal centers.
		/// </summary>
		/// <param name="tex">Texture to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="size">Size to render texture as</param>
		public static void DrawNineSlice( Texture2D tex, Vector2 pos, Vector2 size ) { DrawNineSlice(tex,pos,size,0f); }
		/// <summary>
		/// Draws a textured rectangle to the screen modulated by the set color and it is stretched along the verticle and horizontal centers.
		/// </summary>
		/// <param name="tex">Texture to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="size">Size to render texture as</param>
		/// <param name="depth">Layer to render on</param>
		public static void DrawNineSlice( Texture2D tex, Vector2 pos, Vector2 size, float depth ) {
			int cw = tex.Width / 2 - 1;
			int ch = tex.Height / 2 - 1;
			float xscale = (size.X - (float)tex.Width + 2f) / 2f;
			float yscale = (size.Y - (float)tex.Height + 2f) / 2f;

			// Top-left
			SpriteBatch.Draw(
				tex,
				pos,
				new Rectangle( 0, 0, cw, ch ),
				DrawColor,
				0f,
				Vector2.Zero,
				Vector2.One,
				SpriteEffects.None,
				depth
			);
			
			// Top-right
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X+size.X-cw, pos.Y ),
				new Rectangle( tex.Width-cw, 0, cw, ch ),
				DrawColor,
				0f,
				Vector2.Zero,
				Vector2.One,
				SpriteEffects.None,
				depth
			);

			// Bottom-left
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X, pos.Y+size.Y-ch ),
				new Rectangle( 0, tex.Height-ch, cw, ch ),
				DrawColor,
				0f,
				Vector2.Zero,
				Vector2.One,
				SpriteEffects.None,
				depth
			);

			// Bottom-right
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X+size.X-cw, pos.Y+size.Y-ch ),
				new Rectangle( tex.Width-cw, tex.Height-ch, cw, ch ),
				DrawColor,
				0f,
				Vector2.Zero,
				Vector2.One,
				SpriteEffects.None,
				depth
			);

			// Top
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X+cw, pos.Y ),
				new Rectangle( cw+1, 0, 2, ch ),
				DrawColor,
				0f,
				Vector2.Zero,
				new Vector2( xscale, 1f ),
				SpriteEffects.None,
				depth
			);

			// Bottom
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X+cw, pos.Y+size.Y-ch ),
				new Rectangle( cw+1, tex.Height-ch, 2, ch ),
				DrawColor,
				0f,
				Vector2.Zero,
				new Vector2( xscale, 1f ),
				SpriteEffects.None,
				depth
			);

			// Left
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X, pos.Y+ch ),
				new Rectangle( 0, ch+1, cw, 2 ),
				DrawColor,
				0f,
				Vector2.Zero,
				new Vector2( 1f, yscale ),
				SpriteEffects.None,
				depth
			);

			// Right
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X+size.X-cw, pos.Y+ch ),
				new Rectangle( tex.Width-cw, ch+1, cw, 2 ),
				DrawColor,
				0f,
				Vector2.Zero,
				new Vector2( 1f, yscale ),
				SpriteEffects.None,
				depth
			);

			// Center
			SpriteBatch.Draw(
				tex,
				new Vector2( pos.X+cw, pos.Y+ch ),
				new Rectangle( cw+1, ch+1, 2, 2 ),
				DrawColor,
				0f,
				Vector2.Zero,
				new Vector2( xscale, yscale ),
				SpriteEffects.None,
				depth
			);
		}

		/// <summary>
		/// Draws a string to the screen modulated by the set color
		/// </summary>
		/// <param name="str">String to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="origin">Offset from the texture origin in texture coords to draw and rotate around</param>
		/// <param name="rotation">Rotation</param>
		public static void DrawString( string font, string str, Vector2 pos, Vector2 origin, float rotation ) { DrawString(font,str,pos,origin,rotation,0f); }
		/// <summary>
		/// Draws a string to the screen modulated by the set color
		/// </summary>
		/// <param name="str">String to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="origin">Offset from the texture origin in texture coords to draw and rotate around</param>
		/// <param name="rotation">Rotation</param>
		/// <param name="depth">Layer to render on</param>
		public static void DrawString( string font, string str, Vector2 pos, Vector2 origin, float rotation, float depth ) {
			SpriteFont sfont = Main.FindFont( font );
			SpriteBatch.DrawString( sfont, str, pos, DrawColor, rotation, (origin == Vector2.Zero ? Vector2.Zero : sfont.MeasureString(str)*origin), 1f, SpriteEffects.None, depth );
		}

		/// <summary>
		/// Draws a sprite to the screen modulated by the set color
		/// </summary>
		/// <param name="sprite">Sprite to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="size">Size to render texture as</param>
		/// <param name="origin">Offset from the texture origin in texture coords to draw and rotate around</param>
		/// <param name="rotation">Rotation</param>
		/// <param name="frame">Frame to use when drawing the sprite</param>
		public static void DrawSprite( Sprite sprite, Vector2 pos, Vector2 size, Vector2 origin, float rotation, int frame ) { DrawSprite(sprite,pos,size,origin,rotation,frame,0f); }
		/// <summary>
		/// Draws a sprite to the screen modulated by the set color
		/// </summary>
		/// <param name="sprite">Sprite to draw</param>
		/// <param name="pos">Offset from the screen origin</param>
		/// <param name="size">Size to render texture as</param>
		/// <param name="origin">Offset from the texture origin in texture coords to draw and rotate around</param>
		/// <param name="rotation">Rotation</param>
		/// <param name="frame">Frame to use when drawing the sprite</param>
		/// <param name="depth">Layer to render on</param>
		public static void DrawSprite( Sprite sprite, Vector2 pos, Vector2 size, Vector2 origin, float rotation, int frame, float depth ) {
			Surface.DrawSubRect(
				sprite.Texture,
				pos,
				size,
				sprite.GetFrameRect(frame),
				sprite.Center + origin,
				rotation,
				depth
			);
		}

		public static void DrawCircle( Vector2 pos, float radius, int segments, float weight ) { DrawCircle(pos,radius,segments,weight,0f); }
		public static void DrawCircle( Vector2 pos, float radius, int segments, float weight, float depth ) {
			if( segments < 3 )
				segments = 3;

			Vector2 prevPos;
			Vector2 nextPos;
			float delta = MathHelper.TwoPi / (float)segments;
			for(int i=0;i<segments;i++) {
				float angle = delta * i;
				float angle2 = delta * (i+1);
				prevPos = new Vector2( pos.X+(float)Math.Cos(angle)*radius, pos.Y+(float)Math.Sin(angle)*radius );
				nextPos = new Vector2( pos.X+(float)Math.Cos(angle2)*radius, pos.Y+(float)Math.Sin(angle2)*radius );

				/*if( RenderMode == RenderMode.World ) {
					Vector2 dir = nextPos - prevPos;
					float dist = dir.Length();
					dir.Normalize();

					nextPos = prevPos + (dist * WorldScale) * dir;
				}*/

				Surface.DrawLine( prevPos, nextPos, weight, depth );
			}
		}

		/// <summary>
		/// Converts a world position to a screen position
		/// </summary>
		/// <param name="pos">World coordinates to convert</param>
		/// <returns>Screen coordinates</returns>
		public static Vector2 ToScreen( Vector2 pos ) { bool ignore; return ToScreen(pos,out ignore); }
		public static Vector2 ToScreen( Vector2 pos, out bool inside ) {
			// Offset by the world space translation matrix (Negate player position mostly)
			Matrix mat;
			if( Globals.LocalPlayer != null && Globals.LocalPlayer.CurrentShip != null )
				mat = Matrix.CreateTranslation( new Vector3(Globals.LocalPlayer.CurrentShip.Position*-1,0f) );
			else
				mat = Matrix.Identity;
			pos = Vector2.Transform( pos, mat );

			// Scale by zoom
			float scale = Surface.WorldScale * ZoomScale;
			Vector2 screen = pos * scale;

			// Origin fix (Move from center to top-left)
			screen.X += Surface.Width * 0.5f;
			screen.Y -= Surface.Height * 0.5f;
			screen.Y *= -1f;

			if( screen.X >= 0f && screen.X < Width && screen.Y >= 0f && screen.Y < Height )
				inside = true;
			else
				inside = false;

			return screen;
		}

		// TODO
		public static Vector2 ToWorld( Vector2 pos ) {
			throw new NotImplementedException();
		}

		// ----------
		
		/// <summary>
		/// Returns the viewport width.
		/// </summary>
		public static float Width {
			get { return (s_GraphicsDevice != null ? s_GraphicsDevice.Viewport.Width : 0f); }
		}
		
		/// <summary>
		/// Returns the viewport height.
		/// </summary>
		public static float Height {
			get { return (s_GraphicsDevice != null ? s_GraphicsDevice.Viewport.Height : 0f); }
		}

		/// <summary>
		/// Indicates whether or not Surface is ready to receive draw calls.
		/// </summary>
		public static RenderMode RenderMode { get; private set; }

		// ----------

		private static GraphicsDevice s_GraphicsDevice;
		private static BasicEffect s_SpriteEffect;
		private static Texture2D s_TextureMissing;
		private static Texture2D s_TextureWhite;

		public static SpriteBatch SpriteBatch; // Public to fix some stuff

		/// <summary>
		/// Get or Set the current draw color.
		/// </summary>
		public static Color DrawColor { get; set; }
		
		public static float WorldScale { get; private set; }
		public static float ZoomScale { get; set; }

		public static Matrix OverscanMatrix { get; set; }
		public static Matrix WorldMatrix { get; set; }
		public static Matrix ViewMatrix { get; set; }
		public static Matrix ProjectionMatrix { get; set; }
	}
}
